﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
//using System.Linq;
using System.IO;
using System.Text;
using System.Drawing;

using System.Threading;

using SdlDotNet.Input;
using SdlDotNet.Core;
using SdlDotNet.Graphics;
using SdlDotNet.Graphics.Sprites;


namespace T7G_4_Win
{
    class T7G
    {
        /// <summary>
        /// List of optical drives
        /// </summary>
        public System.IO.DriveInfo[] drives;
        
        /// <summary>
        /// Current drive in use
        /// </summary>
        public int chosenDrive = -1;

        /// <summary>
        /// Groovie script parser
        /// </summary>
        Groovie groovie;
        
        /// <summary>
        /// Tick handler
        /// </summary>
        EventHandler<TickEventArgs> tickHandler;

        /// <summary>
        /// Time between frames
        /// </summary>
        //private readonly float t = 1.0 / (float)15.0; // Force float

        /// <summary>
        /// Time since last frame
        /// </summary>
        private long lastFrame;
        
                
        /// <summary>
        /// Array of cursors available for use
        /// </summary>
        Cursor[] cursors = new Cursor[11];
        
        /// <summary>
        /// Current cursor to use
        /// </summary>
        private byte currCursor = 0;

        #region VDX drawing handling
        /// <summary>
        /// Screen reference
        /// </summary>
        public readonly Surface screen;

        /// <summary>
        /// Surface reflecting central game area
        /// </summary>
        public readonly Surface gameArea;
        //public readonly Surface gameMask;
//        private Rectangle copyArea;

        /// <summary>
        /// Surface to act as back buffer - used for puzzle resets?
        /// </summary>
        public Surface gameBuffer;

        public void copyBuffer()
        {
            gameBuffer.Fill(Color.Black);
            gameBuffer.Blit(gameArea);
        }
            
        public void copyBuffer(ushort left, ushort top, ushort right, ushort bottom)
        {
            Rectangle copyArea = new Rectangle(left, top - 80, right - left + 1, bottom - top + 1);
            gameBuffer.Blit(gameArea, copyArea.Location, copyArea);
        }
        
        #endregion
        
        #region Mouse/keyboard interaction

        /// <summary>
        /// Groovie processing is done until next user input
        /// </summary>
        public bool waitForInput = false;

        /// <summary>
        /// Enumerator relating to the 4 standard mouse hotspots
        /// </summary>
        public enum mouseSpots
        {
            left = 0,
            right,
            top,
            bottom
        }

        /// <summary>
        /// Structure representing hotspot areas on the screen
        /// </summary>
        public struct mouseHotspot
        {
            public Rectangle area;
            public byte cursor;
            public ushort jumpAddress;
        }

        /// <summary>
        /// List of non-standard hotspots related to mouse areas on the screen
        /// </summary>
        private Dictionary<Rectangle, mouseHotspot> hotspots = new Dictionary<Rectangle, mouseHotspot>();

        /// <summary>
        /// List of the four "standard" mouse hotspots
        /// </summary>
        private mouseHotspot[] stdspot = new mouseHotspot[4];

        /// <summary>
        /// List of available keypresses
        /// </summary>
        private Dictionary<byte, ushort> keyspots = new Dictionary<byte, ushort>();

        /// <summary>
        /// Add a hotspot to monitor
        /// </summary>
        /// <param name="area">Area of hotspot</param>
        /// <param name="cursor">Cursor to show on hover</param>
        /// <param name="jumpAddress">GRV address to jump to</param>
        public void addHotspot(Rectangle area, byte cursor, ushort jumpAddress)
        {
            mouseHotspot t;
            // If hotspot has been pre-added, overwrite old value if necessary
            if (hotspots.ContainsKey(area))
            {
                hotspots.TryGetValue(area, out t);
                if (t.jumpAddress == jumpAddress && t.cursor == cursor)
                    return;
                else
                    hotspots.Remove(area);
            }
            // Else replace it
            t = new mouseHotspot();
            t.area = area;
            t.cursor = cursor;
            t.jumpAddress = jumpAddress;

            hotspots.Add(area, t);
        }

        /// <summary>
        /// Add hotkey
        /// </summary>
        /// <param name="key">Key ID to check for</param>
        /// <param name="jumpAddress">GRV address to jump to</param>
        public void addHotspot(byte key, ushort jumpAddress)
        {
            ushort addr;
            // Same for key presses - check if it's been already added ...
            if (keyspots.ContainsKey(key))
            {
                keyspots.TryGetValue(key, out addr);
                if (addr == jumpAddress)
                    return;
                else
                    keyspots.Remove(key);
            }
            // ... and replace if necessary
            keyspots.Add(key, jumpAddress);
            Console.WriteLine("Add keyspot {0} (0x{0:X} - \"{1}\")", key, (char)key);
        }

        /// <summary>
        /// Updates one of the 4 standard hotspots
        /// </summary>
        /// <param name="area"></param>
        /// <param name="cursor"></param>
        /// <param name="jumpAddress"></param>
        public void standardHotspot(mouseSpots spot, byte? cursor, ushort? jumpAddress)
        {
            mouseHotspot t = stdspot[(int)spot];
            // Allow cursor or jump address updates independantly
            t.cursor = cursor ?? t.cursor;
            t.jumpAddress = jumpAddress ?? t.jumpAddress;
            stdspot[(int)spot] = t;
        }

        /// <summary>
        /// Remove a mouse hotspot
        /// </summary>
        /// <param name="area">Area of hotspot to remove</param>
        public void removeHotspot(Rectangle area)
        {
            hotspots.Remove(area);
            Console.WriteLine("Removing hotspot " + area);
        }

        /// <summary>
        /// Remove hotkey
        /// </summary>
        /// <param name="key">Key ID to remove</param>
        public void removeHotspot(byte key)
        {
            keyspots.Remove(key);
            Console.WriteLine("Removing keyspot " + key);
        }

        /// <summary>
        /// Remove all hotspots
        /// </summary>
        public void removeHotspot()
        {
            //if (hotspots.Count > 0)
                hotspots.Clear();
            //if (keyspots.Count > 0)
                keyspots.Clear();
        }

        #endregion
        
        public static void Main()
        {
            T7G main = new T7G();
        }

        /// <summary>
        /// Create a T7G game window
        /// </summary>
        public T7G()
        {
            // "Fix" T7G path
            if (!Properties.Settings.Default.T7GPath.EndsWith("\\"))
            {
                Properties.Settings.Default.T7GPath = Properties.Settings.Default.T7GPath + "\\";
                Properties.Settings.Default.Save();
            }

            // Since we can't set it anyway, manually hard-code temporarily
            Properties.Settings.Default.T7GPath = @"D:\Utils\Done\t7g\cd2\T7G\INSTALL\";

            // Initialize screen settings
            bool fullScreen = false;
            bool hardware = true;
            bool resize = false;
            bool oGL = false;
            screen = Video.SetVideoMode(640, 480, resize, oGL, fullScreen, hardware);
            // Fiddle with screen properties
            screen.Transparent = true;
            screen.TransparentColor = Color.Black;
            gameArea = new Surface(640, 320, 32);
            gameArea.Transparent = true;
            gameArea.TransparentColor = Color.Black;
            gameBuffer = new Surface(640, 320, 32);
            /*gameMask = new Surface(640, 320, 32);
            gameMask.Transparent = true;
            gameMask.TransparentColor = Color.Blue;
            gameMask.Fill(Color.Blue);*/
            // Caption
            Video.WindowCaption = "T7G4WIN - SDL version";

            //Event handling
            Events.Quit += new EventHandler<QuitEventArgs>(this.Quit);
            Events.MouseButtonDown += new EventHandler<MouseButtonEventArgs>(this.MouseClick);
            tickHandler = new EventHandler<TickEventArgs>(this.Tick);
            Events.Tick += tickHandler;
            Events.Tick += new EventHandler<TickEventArgs>(this.Tick);
            Events.KeyboardDown += new EventHandler<KeyboardEventArgs>(this.KeyPress);
            
            // Get optical drives
            System.IO.DriveInfo[] d = System.IO.DriveInfo.GetDrives();
            Queue<System.IO.DriveInfo> s = new Queue<System.IO.DriveInfo>();
            foreach (System.IO.DriveInfo drive in d)
            {
                if (drive.DriveType == System.IO.DriveType.CDRom)
                    s.Enqueue(drive);
            }
            drives = s.ToArray();
            
            // Init mouse hotspots
            stdspot[(int)(mouseSpots.left)].area = new Rectangle(0, 80, 100, 320);
            stdspot[(int)(mouseSpots.right)].area = new Rectangle(540, 80, 100, 320);
            stdspot[(int)(mouseSpots.top)].area = new Rectangle(0, 0, 640, 80);
            stdspot[(int)(mouseSpots.bottom)].area = new Rectangle(0, 400, 640, 80);

            // Hide windows cursor
            Mouse.ShowCursor = false;

            // Initialize game cursors
            for (byte i = 0; i < 11; i++)
                cursors[i] = new Cursor(i);

            // Target FPS
            Events.Fps = 60;
            
            // Load up the Groovie script
            groovie = new Groovie("script.grv", this);

            lastFrame = System.DateTime.Now.Ticks;

            // Start the ball rolling
            Events.Run();
            
            
        }

        private void Tick(object sender, TickEventArgs e)
        {
            if (groovie == null)
                return;

            Point p = new Point(0, 80);

            long t;
            //TimeSpan time = DateTime.Now.TimeOfDay.Subtract(sinceLastFrame);
            // Clear screen
            screen.Fill(Color.Black);
            //gameArea.Fill(Color.Black);

            // Set to default cursor
            currCursor = 5;

            // Update groovie frame if present
            VDX vdx = groovie.getVDX();
            if (vdx != null)
            {
                // Clear screen
                //screen.TransparentColor = vdx.transparentColor;
                //gameArea.TransparentColor = vdx.transparentColor;
                //screen.Fill(screen.TransparentColor);
                //gameArea.Fill(gameArea.TransparentColor);
                t = (long)(1000 / (float)(vdx.fps+1) * TimeSpan.TicksPerMillisecond);
                long now = System.DateTime.Now.Ticks;
                if ((vdx.playing == true) && ((lastFrame + t) <= now))
                {
                    lastFrame = now;
                    vdx.doTick();
                    //sinceLastFrame = DateTime.Now.TimeOfDay; //0;

                }

                if (vdx.isBG)
                    gameBuffer.Blit(vdx.surface);
                else
                    gameArea.Blit(vdx.surface);


            }
            else
            {
                t = (long)(1000 / (float)(15.0) * TimeSpan.TicksPerMillisecond);
            }
            
            screen.Blit(gameBuffer, p);
            screen.Blit(gameArea, p);

            // Execute a Groovie op-code
            if (waitForInput == false)
                groovie.execute();  // But only if we still have parsing to do

            // Show hotspots
            if (waitForInput)
            {
                foreach (mouseHotspot m in stdspot)
                {
                    if (m.jumpAddress == 0)
                        continue;

                    Surface s = new Surface(m.area);
                    s.Fill(Color.White);
                    s.Alpha = 40;
                    s.AlphaBlending = true;
                    screen.Blit(s, new Point(m.area.X, m.area.Y));
                    s.Dispose();
                    if (m.area.Contains(Mouse.MousePosition))
                    {
                        currCursor = m.cursor;
                        break;
                    }
                }


                Dictionary<Rectangle, mouseHotspot> d = new Dictionary<Rectangle, mouseHotspot>(hotspots);
                foreach (mouseHotspot m in d.Values)
                {
                    Surface s = new Surface(m.area);
                    s.Fill(Color.White);
                    s.Alpha = 40;
                    s.AlphaBlending = true;
                    screen.Blit(s, new Point(m.area.X, m.area.Y));
                    s.Dispose();
                    if (m.area.Contains(Mouse.MousePosition))
                    {
                        currCursor = m.cursor;
                        break;
                    }
                }

                {
                    long now = System.DateTime.Now.Ticks;
                    if ((lastFrame + t) <= now)
                    {
                        lastFrame = now;
                        cursors[currCursor].animate();
                    }
                    p = SdlDotNet.Input.Mouse.MousePosition;
                    Surface s = cursors[currCursor].surface;
                    p.X -= s.Width / 2;
                    p.Y -= s.Height / 2;
                    screen.Blit(cursors[currCursor].surface, p);
                }
            }
                        
            screen.Update();
        }
        
        private void KeyPress(object sender, KeyboardEventArgs e)
        {
            try
            {
                foreach (byte b in keyspots.Keys)
                {
                    string c = ((char)b).ToString();
                    if ((e.Mod & (ModifierKeys.ShiftKeys | ModifierKeys.Caps)) != 0)
                        c = c.ToLower();

                    if ((e.KeyboardCharacter == c) || (e.KeyboardCharacter == "space" && c == " "))
                    {
                        ushort addr;
                        keyspots.TryGetValue(b, out addr);
                        groovie.mouseJump(addr);
                        waitForInput = false;
                    }
                }
            }
            catch
            {

            }
        }
        
        private void MouseClick(object sender, MouseButtonEventArgs e)
        { /*
            if (e.Button == MouseButton.PrimaryButton)
                groovie.setClick(e);
        }*/
            // Check clicks
            if (waitForInput && e.Button == MouseButton.PrimaryButton && e.ButtonPressed == true)
            {
                Point mouse = new Point(e.X, e.Y);
                try
                {
                    foreach (Rectangle r in hotspots.Keys)
                    {
                        if (r.Contains(mouse))
                        {
                            mouseHotspot t;
                            hotspots.TryGetValue(r, out t);
                            groovie.mouseJump(t.jumpAddress);
                            waitForInput = false;
                            break;
                        }
                    }

                    // Check non-standard first due to overlap
                    foreach (mouseHotspot m in stdspot)
                    {
                        if (m.jumpAddress > 0 && m.area.Contains(mouse))
                        {
                            groovie.mouseJump(m.jumpAddress);
                            waitForInput = false;
                        }
                    }
                }
                catch 
                {
                    // A hotspot was removed somewhere
                }
            
            }
        }
        
        private void Quit(object sender, QuitEventArgs e)
        {
            Events.Tick -= tickHandler;
            Exit();
        }

        public void Exit()
        {
            //g.stop();
            //g = null;

            /*VDX[] items = drawList.ToArray();
            foreach (VDX vdx in items)
            {
                vdx.stop();
                drawList.Remove(vdx);
            }
            */
            groovie.stop();
            groovie = null;
            Events.QuitApplication();
        }

    }
}
